package com.yami.shop.util;

import com.alibaba.fastjson.JSON;
import com.yami.shop.bean.dto.red.RedPacketBasic;
import com.yami.shop.common.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: huangyibo
 * @Date: 2022/6/23 17:59
 * @Description: 拆红包工具类 二倍均值法(微信红包采用此法)
 * 还是以10元10个红包为例，去除每个红包的最小金额后，红包剩余9.9元，二倍均值计算公式：2 * 剩余金额/剩余红包数
 * 第一个红包在[0,1.98]范围随机，假设随机得1.9，则第一个红包金额为2.0，红包剩余8元。
 * 第二个红包在[0,2]范围随机，假设随机的1元，则第二个红包金额为1.1元，红包剩余7元。
 * 第三个红包在[0,2]范围随机，假设随机的0.5元，则第三个红包金额为0.6元，红包剩余5.5元。
 * 以此类推。
 * <p>
 * 此工具类使用版本——原理
 * 剩余红包金额M，剩余人数N，那么：每次抢到金额=随机(0，M/N*2)
 * 保证了每次随机金额的平均值是公平的
 * 假设10人，红包金额100元
 * 第一人：100/10*2=20，随机范围(0,20)，平均可以抢到10元
 * 第二人：90/9*2=20，随机范围(0,20)，平均可以抢到10元
 * 第三人：80/8*2=20，随机范围(0,20)，平均可以抢到10元
 * 以此类推，每次随机范围的均值是相等的
 * <p>
 * 缺点：除了最后一次，任何一次抢到的金额都不会超过人均金额的两倍，并不是任意的随机
 */

@Component
@Slf4j
public class RedPacketUtils {

    /**
     * 红包的缓存key
     */
    public static final String RED_PACKAGE_KEY = "RED_PACKAGE_KEY_";

    /**
     * 抢红包资格缓存key
     */
    public static final String GRAB_RED_PACKAGE_KEY = "GRAB_RED_PACKAGE_KEY_";

    /**
     * 拆红包的分布式锁key
     */
    public static final String UNPACK_RED_PACKAGE_KEY = "UNPACK_RED_PACKAGE_KEY_";

    /**
     * 抢中红包的缓存key
     */
    public static final String RED_PACKAGE_CONSUME_KEY = "RED_PACKAGE_CONSUME_KEY_";

    /**
     * 红包缓存时效时间(没人抢、最大为一天, 红包抢完缓存主动删除)，毫秒
     */
    public static final int RED_PACKET_EXPIRE = 24 * 60 * 60;

    /**
     * 拆红包核心接口
     *
     * @param redPacketId 红包id
     * @param userId      用户id
     * @return
     */
    public Integer unpackRedPacket(String redPacketId, String userId) {
        Integer redPacketAmount = null;
        try {
            boolean tryLock = RedisUtil.tryLock(UNPACK_RED_PACKAGE_KEY + redPacketId);
            if (!tryLock) {
                //分布式锁获取失败, 将抢红包资格加回去, 让其他人可以去抢
                RedisUtil.incr(GRAB_RED_PACKAGE_KEY + redPacketId, 1);
                return redPacketAmount;
            }
            //红包信息缓存
            String redPackageKey = RED_PACKAGE_KEY + redPacketId;

            //获取红包相关信息
            RedPacketBasic redPacket = (RedPacketBasic) RedisUtil.get(redPackageKey);
            if (redPacket == null) {
                return redPacketAmount;
            }
            //红包剩下一个
            if (redPacket.getTotalPacket() == 1) {
                redPacket.setTotalPacket(redPacket.getTotalPacket() - 1);
                redPacketAmount = redPacket.getTotalAmount();
                redPacket.setTotalAmount(0);
            }

            if (redPacket.getTotalPacket() > 1) {
                //红包金额以分为单位, 使用random.nextInt(Integer)，最后在再除以100
                //这样就可以保证每个人抢到的金额都可以精确到小数点后两位
                Integer restAmount = redPacket.getTotalAmount();

                Random random = new Random();

                // 随机范围：[1，剩余人均金额的两倍)，左闭右开
                redPacketAmount = random.nextInt(restAmount / redPacket.getTotalPacket() * 2 - 1) + 1;
                if(redPacketAmount > 800 || ((redPacket.getTotalAmount() - redPacketAmount) / (redPacket.getTotalPacket() - 1) > 800) ){
                    redPacketAmount = 800;
                }

                //红包数除以100
                redPacket.setTotalAmount(redPacket.getTotalAmount() - redPacketAmount);
                redPacket.setTotalPacket(redPacket.getTotalPacket() - 1);
            }

            //成功抢到红包
            if (redPacketAmount != null && redPacketAmount > 0) {
                //加入已抢到红包的hash缓存中
                RedisUtil.hSet(RED_PACKAGE_CONSUME_KEY + redPacketId, userId, redPacketAmount);

                // 将剩余红包个数和剩余总金额存入缓存
                RedisUtil.set(RedPacketUtils.RED_PACKAGE_KEY + redPacketId, redPacket, RedPacketUtils.RED_PACKET_EXPIRE);
            }

            //红包被抢完，清除缓存
            if (redPacket.getTotalPacket() <= 0) {
                log.info("红包已被抢完, redPacketId:{}, redPacket:{}", redPacketId, JSON.toJSONString(redPacket));
                //抢红包资格缓存key
                String grabRedPackageKey = GRAB_RED_PACKAGE_KEY + redPacketId;
                //抢中红包的缓存key
                String redPackageConsumeKey = RED_PACKAGE_CONSUME_KEY + redPacketId;

                //清楚该红包相关的缓存
                RedisUtil.del(redPackageKey, grabRedPackageKey, redPackageConsumeKey);
            }
            log.info("抢红包SUCCESS, redPacketId:{}, redPacketAmount:{}", redPacketId, redPacketAmount);
            return redPacketAmount;
        } catch (Exception e) {
            log.error("抢红包发生异常", e);
            throw e;
        } finally {
            RedisUtil.unLock(UNPACK_RED_PACKAGE_KEY + redPacketId);
        }
    }

    public static void main(String[] args) {
//        Long startTi = System.currentTimeMillis();
//        List<BigDecimal> li = genRanddomList(new BigDecimal(500), 3, new BigDecimal(0.01), new BigDecimal(200),0f);
//        li = randomArrayList(li);
//
//        System.out.println("======size=========size:" + li.size());
//        System.out.println(li);
//        Long endTi = System.currentTimeMillis();
//        System.out.println("======耗时=========:" + (endTi - startTi) / 1000 + "秒");

        int a = 3900;
        int a1 = 5;

        for (int i = a1; i > 0; i--) {
            if (a1 == 1) {
                System.out.println(a);
            } else {
                Random random = new Random();
                int b = 0;
                if (a / a1 == 800) {
                    b = 800;
                } else {
                    b = random.nextInt(a / a1 * 2 - 1) + 1;
                    if(b >800 || ((a-b) / (a1-1)) > 800 ){
                        b = 800;
                    }
                }
                System.out.println(b);
                a = a - b;
            }
            a1--;
        }
    }

    public static List<Integer> genRandList(int total, int splitNum, int min, int max, float thresh) {
        Random random = new Random();
        assert total >= splitNum * min && total <= splitNum * max : "请校验红包参数设置的合理性";
        assert thresh >= 0.0f && thresh <= 1.0f;
        // 平均分配
        int average = total / splitNum;
        List<Integer> list = new ArrayList<>(splitNum);
        int rest = total - average * splitNum;
        for (int i = 0; i < splitNum; i++) {
            if (i < rest) {
                list.add(average + 1);
            } else {
                list.add(average);
            }
        }
        // 如果浮动阀值为0则不进行数据随机处理
        if (thresh == 0) {
            return list;
        }
        // 根据阀值进行数据随机处理
        int randOfRange = 0;
        int randRom = 0;
        int nextIndex = 0;
        int nextValue = 0;
        int surplus = 0;//多余
        int lack = 0;//缺少
        for (int i = 0; i < splitNum - 1; i++) {
            nextIndex = i + 1;
            int itemThis = list.get(i);
            int itemNext = list.get(nextIndex);
            boolean isLt = itemThis < itemNext;
            int rangeThis = isLt ? max - itemThis : itemThis - min;
            int rangeNext = isLt ? itemNext - min : max - itemNext;
            int rangeFinal = (int) Math.ceil(thresh * (Math.min(rangeThis, rangeNext) + 100));
            randOfRange = random.nextInt(rangeFinal);
            randRom = isLt ? 1 : -1;
            int iValue = list.get(i) + randRom * randOfRange;
            nextValue = list.get(nextIndex) + randRom * randOfRange * -1;
            if (iValue > max) {
                surplus += (iValue - max);
                list.set(i, max);
            } else if (iValue < min) {
                list.set(i, min);
                lack += (min - iValue);
            } else {
                list.set(i, iValue);
            }
            list.set(nextIndex, nextValue);
        }
        if (nextValue > max) {
            surplus += (nextValue - max);
            list.set(nextIndex, max);
        }
        if (nextValue < min) {
            lack += (min - nextValue);
            list.set(nextIndex, min);
        }
        if (surplus - lack > 0) {//钱发少了 给低于max的凑到max
            for (int i = 0; i < list.size(); i++) {
                int value = list.get(i);
                if (value < max) {
                    int tmp = max - value;
                    if (surplus >= tmp) {
                        surplus -= tmp;
                        list.set(i, max);
                    } else {
                        list.set(i, value + surplus);
                        return list;
                    }
                }
            }
        } else if (lack - surplus > 0) {//钱发多了 给超过高于min的人凑到min
            for (int i = 0; i < list.size(); i++) {
                int value = list.get(i);
                if (value > min) {
                    int tmp = value - min;
                    if (lack >= tmp) {
                        lack -= tmp;
                        list.set(i, min);
                    } else {
                        list.set(i, min + tmp - lack);
                        return list;
                    }
                }
            }
        }
        return list;
    }

    public static List<BigDecimal> genRanddomList(BigDecimal totalMoney, Integer splitNum, BigDecimal min, BigDecimal max, Float thresh) {
        totalMoney = totalMoney.multiply(new BigDecimal(100));
        min = min.multiply(new BigDecimal(100));
        max = max.multiply(new BigDecimal(100));
        List<Integer> li = genRandList(totalMoney.intValue(), splitNum, min.intValue(), max.intValue(), thresh);
        List<BigDecimal> randomList = new CopyOnWriteArrayList<>();
        for (Integer v : li) {
            BigDecimal randomVlue = new BigDecimal(v).divide(new BigDecimal(100));
            randomList.add(randomVlue);
        }

        randomList = randomArrayList(randomList);
        return randomList;
    }

    public static List<BigDecimal> randomArrayList(List<BigDecimal> sourceList) {
        if (sourceList == null || sourceList.isEmpty()) {
            return sourceList;
        }
        List<BigDecimal> randomList = new CopyOnWriteArrayList<>();
        do {
            int randomIndex = Math.abs(new Random().nextInt(sourceList.size()));
            randomList.add(sourceList.remove(randomIndex));
        } while (sourceList.size() > 0);

        return randomList;
    }
}
